package com.iflytek.demo.ability.tts;

import com.iflytek.demo.ability.Tools;
import ohos.app.Context;
import ohos.utils.zson.ZSONObject;
import okhttp3.*;

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
import java.util.TimeZone;

/**
 * 讯飞开放平台 语音合成客户端 【 语音合成（流式版）WebAPI 】
 * https://www.xfyun.cn/doc/tts/online_tts/API.html
 *
 * @author zhhuang10
 * @date 2021/7/2
 */
public class TtsClient {
    // 服务器地址 【固定的】
    private final String mHostUrl = "wss://tts-api.xfyun.cn/v2/tts";
    private final String mHost = "tts-api.xfyun.cn";
    private final String mPath = "/v2/tts";

    // 账号参数
    private String mAppId;
    private String mApiSecret;
    private String mApiKey;

    // WebSocket
    private WebSocket mWebSocket;
    // 回调
    private TtsClient.CallBack mCallBack;
    // 合成的文本
    private String mTtsText;
    // 发音人
    private String mSpeaker = "xiaoyan";
    // 播放器
    private TtsPcmPlayer mPcmPlayer;
    // 是否取消任务
    private boolean mCancel = false;
    // 是否已经停止播放
    private boolean stopPlay;
    private Context mContext;


    public TtsClient(Context context) {
        mContext = context;
    }

    public void setContext(Context mContext) {
        this.mContext = mContext;
    }

    public final void setCallBack(TtsClient.CallBack callBack) {
        this.mCallBack = callBack;
    }

    public final void setSpeaker(String speaker) {
        this.mSpeaker = speaker;
    }

    public void setAppId(String appId) {
        this.mAppId = appId;
    }

    public void setApiSecret(String apiSecret) {
        this.mApiSecret = apiSecret;
    }

    public void setApiKey(String apiKey) {
        this.mApiKey = apiKey;
    }

    /**
     * 合成并播放
     */
    public final void speak(String ttsText) {
        {
            mTtsText = ttsText;
            mCancel = false;
            stopPlay = false;
        }
        String authUrl = this.createAuthUrl(this.mApiKey, this.mApiSecret);
        Request request = (new Request.Builder()).url(authUrl).build();
        OkHttpClient client = Tools.okHttp();
        this.mWebSocket = client.newWebSocket(request, new WebSocketListener() {
            public void onOpen(WebSocket webSocket, Response response) {
                sendFirst();
            }

            public void onMessage(WebSocket webSocket, String text) {
                TtsResponseData resp;
                try {
                    resp = ZSONObject.stringToClass(text, TtsResponseData.class);
                } catch (Exception ex) {
                    resp = null;
                }
                if (resp == null) {
                    return;
                }
                if (resp.code != 0) {
                    return;
                }
                // resp.data.status == 2 说明数据全部返回完毕，可以关闭连接，释放资源
                if (resp.data != null && resp.data.status == 2) {
                    webSocket.close(1000, "ok");
                }
                if (mCallBack != null && resp.data != null && resp.data.audio != null) {
                    byte[] bytes = Tools.base64Decode(resp.data.audio);
                    boolean isComplete = resp.data.status == 2;
                    playAudio(bytes, isComplete);
                    mCallBack.onResult(bytes, isComplete);
                }
            }

            public void onClosed(WebSocket webSocket, int code, String reason) {
                mWebSocket = null;
            }

            public void onFailure(WebSocket webSocket, Throwable t, Response response) {
                if (mCallBack != null) {
                    mCallBack.onError(1);
                }
            }
        });
    }

    /**
     * 发送合成数据包
     */
    public final void sendFirst() {
        if (mWebSocket == null) {
            return;
        }
        ZSONObject zsonObject = new ZSONObject();
        {
            ZSONObject common = new ZSONObject();
            common.put("app_id", mAppId);
            zsonObject.put("common", common);
        }
        {
            ZSONObject business = new ZSONObject();
            // 音频类型
            business.put("aue", "raw");
            // pcm 音频参数
            business.put("auf", "audio/L16;rate=16000");
            business.put("tte", "UTF8");
            // 发音人
            business.put("vcn", mSpeaker);
            // 语速
            business.put("speed", 62);
            // 音量
            business.put("volume", 98);
            // 音调
            business.put("pitch", 50);
            zsonObject.put("business", business);
        }
        {
            ZSONObject data = new ZSONObject();
            data.put("status", 2);
            // 带合成的string
            data.put("text", Tools.base64(mTtsText));
            zsonObject.put("data", data);
        }
        mWebSocket.send(zsonObject.toString());
    }

    /**
     * 鉴权
     */
    private String createAuthUrl(String apiKey, String apiSecret) {
        SimpleDateFormat format = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss z", Locale.US);
        format.setTimeZone(TimeZone.getTimeZone("GMT"));
        String date = format.format(new Date());
        String signatureOrigin = "host: " + this.mHost + "\ndate: " + date + "\nGET " + this.mPath + " HTTP/1.1";
        String sha = Tools.base64(Tools.hmacsha256(signatureOrigin.getBytes(), apiSecret.getBytes()));
        String authorization = "api_key=\"" + apiKey + "\", algorithm=\"hmac-sha256\", headers=\"host date request-line\", signature=\"" + sha + '"';
        String authBase64 = Tools.base64(authorization);
        return this.mHostUrl + "?authorization=" + authBase64 + "&date=" + date + "&host=" + this.mHost;
    }

    /**
     * 取消
     */
    public final void cancel() {
        mCancel = true;
        if (mWebSocket != null) {
            mWebSocket.close(1000, "cancel");
            mCallBack = null;
        }
        releasePlayAudio();
    }

    /**
     * 播放音频
     */
    private void playAudio(byte[] bytes, boolean isComplete) {
        if (mCancel || stopPlay) {
            return;
        }
        if (mPcmPlayer == null) {
            mPcmPlayer = new TtsPcmPlayer(mContext);
            mPcmPlayer.setPlayListener(new PlayAudioListener() {
                @Override
                public void onPlayStart() {
                    if (mCallBack != null) {
                        mCallBack.onPlayStart();
                    }
                }

                @Override
                public void onPlayCompletion() {
                    if (mCallBack != null) {
                        mCallBack.onPlayCompletion();
                    }
                }
            });
            mPcmPlayer.startPlay();
        }
        mPcmPlayer.put(bytes);
        if (isComplete) {
            mPcmPlayer.finishPut();
        }
    }

    /**
     * 取消播放
     */
    public void stopPlayAudio() {
        stopPlay = true;
        if (mPcmPlayer == null) {
            return;
        }
        mPcmPlayer.stop();
    }

    /**
     * 取消播放
     */
    public void releasePlayAudio() {
        if (mPcmPlayer == null) {
            return;
        }
        stopPlayAudio();
        mPcmPlayer.release();
        mPcmPlayer = null;
    }

    /**
     * CallBack
     */
    public interface CallBack {
        /**
         * onResult
         */
        void onResult(byte[] audio, boolean isComplete);


        /**
         * 播放开始回调
         */
        void onPlayStart();


        /**
         * 播放结束回调
         */
        void onPlayCompletion();

        /**
         * onError
         */
        void onError(int code);
    }

    public static class TtsResponseData {
        public int code = 0;
        public String message;
        public String sid;
        public TtsData data;
    }

    public static class TtsData {
        public int status = 0;
        public String audio;
    }

}
